﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Collections.ObjectModel;
using System.Xml.Serialization;
using System.ComponentModel;

namespace XEvents.Library
{
    public class EventSession : XEventBase
    {
        #region Constructors
        public EventSession(SqlServer server)
            : base(server)
        { }


        public EventSession(SqlServer server, EventRetentionMode eventretentionmode, 
            int maxdispatchlatency, int maxmemory, int maxeventsize, 
            MemoryPartitionMode memorypartitionmode, TrackCausality trackcausality,
            StartupState startupstate)
            : base(server)
        {
            this.event_retention_mode = eventretentionmode;
            this.max_dispatch_latency = maxdispatchlatency;
        }

        private EventSession()
            : base(null)
        { }

        internal EventSession(server_event_session ses, SqlServer server)
            : base(server)
        {
            this.event_session_id = ses.event_session_id;
            this.name = ses.name;
            this.event_retention_mode = (EventRetentionMode)Enum.Parse(typeof(EventRetentionMode), ses.event_retention_mode_desc, true);
            this.max_dispatch_latency = (ses.max_dispatch_latency / 1000);
            this.max_memory = ses.max_memory;
            this.max_event_size = ses.max_event_size;
            this.memory_partition_mode = (MemoryPartitionMode)Enum.Parse(typeof(MemoryPartitionMode), ses.memory_partition_mode_desc, true);
            this.track_causality = (TrackCausality)Enum.Parse(typeof(TrackCausality), Convert.ToInt32(ses.track_causality).ToString());
            this.startup_state = (StartupState)Enum.Parse(typeof(StartupState), Convert.ToInt32(ses.startup_state).ToString());

            this.targets = new EventSessionTargetCollection(from st in Server.sessiondata.server_event_session_targets
                                                            where st.event_session_id == ses.event_session_id
                                                            select new EventSessionTarget(st, this));
            this.events = new EventSessionEventCollection(from se in Server.sessiondata.server_event_session_events
                                                          where se.event_session_id == ses.event_session_id
                                                          select new EventSessionEvent(se, this));

            this.current_state = (SessionState)Enum.Parse(typeof(SessionState), Convert.ToInt32((from d in server.sessiondata.sessions
                                                                                                 where d.name == this.name
                                                                                                 select d).Count() == 1).ToString());
        }
        #endregion

        #region Private Members

        internal int event_session_id = -1;
        internal string name;
        internal EventRetentionMode event_retention_mode = EventRetentionMode.ALLOW_MULTIPLE_EVENT_LOSS;
        internal int max_dispatch_latency = 300;
        internal int max_memory = 4096;
        internal int max_event_size = 0;
        internal MemoryPartitionMode memory_partition_mode = MemoryPartitionMode.NONE;
        internal TrackCausality track_causality = TrackCausality.OFF;
        internal StartupState startup_state = StartupState.OFF;
        internal SessionState current_state = SessionState.Stop;
        internal bool isdirty = false;
        internal bool requiresdrop = false;

        internal EventSessionTargetCollection targets = new EventSessionTargetCollection();
        internal EventSessionEventCollection events = new EventSessionEventCollection();

        //internal List<string> DataColumns
        //{
        //    get
        //    {
        //        List<string> result = new List<string>();

        //        // collect event columns
        //        result.AddRange(
        //            (from s in Server.sessiondata.server_event_sessions
        //             from e in Server.sessiondata.server_event_session_events
        //             from p in Server.MetaData.packages
        //             from c in Server.MetaData.object_columns
        //             where s.name == this.name 
        //                 && e.event_session_id == s.event_session_id
        //                 && p.name == e.package
        //                 && c.object_package_guid == p.guid 
        //                 && c.object_name == e.name
        //                && c.column_type != "readonly"
        //            select c.name).ToList());

        //        // collect any action columns
        //        result.AddRange(
        //             (from s in Server.sessiondata.server_event_sessions
        //              from a in Server.sessiondata.server_event_session_actions
        //              where s.name == this.name
        //                  && a.event_session_id == s.event_session_id
        //              select a.name).ToList());

        //        return result.Distinct().ToList();
        //    }
        //}

        //internal TargetData targetdata;

        #endregion

        #region Public Properties

        public bool IsDirty
        {
            get { return isdirty; }
        }

        [CategoryAttribute("Session Objects"), DescriptionAttribute("")]
        public EventSessionEventCollection Events
        {
            get 
            { 
                return events; 
            }
        }

        public override string ToString()
        {
            return this.Name;
        }

        [CategoryAttribute("Session Objects"), DescriptionAttribute("")]
        public EventSessionTargetCollection Targets
        {
            get
            {
                return targets;
            }
        }

        /// <summary>
        /// Event Session Name
        /// </summary>
        [XmlAttribute("Name")]
        public string Name
        {
            get { return name; }
            set
            {
                name = value;
                if (event_session_id > -1)
                {
                    isdirty = true;
                    requiresdrop = true;
                }
            }
        }

        /// <summary>
        /// Parent SqlServer for this Session
        /// </summary>
        [XmlIgnore()]
        public SqlServer SqlServer
        {
            get { return this.Server; }
        }

        /// <summary>
        /// Event Session Id
        /// </summary>
        [XmlAttribute("EventSessionId")]
        public int EventSessionId
        {
            get { return event_session_id; }
        }

        /// <summary>
        /// Determines how event loss is handled. 
        /// </summary>
        [CategoryAttribute("Session Options"), DescriptionAttribute("Determines how event loss is handled.")]
        [XmlAttribute("EventRetentionMode")]
        public EventRetentionMode EventRetentionMode
        {
            get { return event_retention_mode; }
            set
            {
                event_retention_mode = value;
                if (event_session_id > -1)
                {
                    isdirty = true;
                    requiresdrop = true;
                }
            }
        }

        /// <summary>
        /// Specifies the amount of time that events will be buffered in memory before being dispatched to event session targets. By default, this value is set to 30 seconds.
        /// </summary>
        [CategoryAttribute("Session Options"), DescriptionAttribute("Specifies the amount of time that events will be buffered in memory before being dispatched to event session targets.")]
        [XmlAttribute("MaxDispatchLatency")]
        public int MaxDispatchLatency
        {
            get { return max_dispatch_latency; }
            set
            {
                max_dispatch_latency = value;
                if (event_session_id > -1)
                {
                    isdirty = true;
                    requiresdrop = true;
                }
            }
        }

        /// <summary>
        /// Specifies the maximum amount of memory to allocate to the session for event buffering. 
        /// </summary>
        [CategoryAttribute("Session Options"), DescriptionAttribute("Specifies the maximum amount of memory to allocate to the session for event buffering.")]
        [XmlAttribute("MaxMemory")]
        public int MaxMemory
        {
            get { return max_memory; }
            set
            {
                max_memory = value;
                if (event_session_id > -1)
                {
                    isdirty = true;
                    requiresdrop = true;
                }
            }
        }

        /// <summary>
        /// Specifies the maximum allowable size for events. MAX_EVENT_SIZE should only be set to allow single events larger than MAX_MEMORY; setting it to less than MAX_MEMORY will raise an error. size is a whole number and can be a kilobyte (KB) or a megabyte (MB) value.
        /// </summary>
        [CategoryAttribute("Session Options"), DescriptionAttribute("Specifies the maximum allowable size for events. MAX_EVENT_SIZE should only be set to allow single events larger than MAX_MEMORY; setting it to less than MAX_MEMORY will raise an error. size is a whole number and can be a kilobyte (KB) or a megabyte (MB) value.")]
        [XmlAttribute("MaxEventSize")]
        public int MaxEventSize
        {
            get { return max_event_size; }
            set
            {
                max_event_size = value;
                if (event_session_id > -1)
                {
                    isdirty = true;
                    requiresdrop = true;
                }
            }
        }

        /// <summary>
        /// Specifies the location where event buffers are created.
        /// </summary>
        [CategoryAttribute("Session Options"), DescriptionAttribute("Specifies the location where event buffers are created.")]
        [XmlAttribute("MemoryPartitionMode")]
        public MemoryPartitionMode MemoryPartitionMode
        {
            get { return memory_partition_mode; }
            set
            {
                memory_partition_mode = value;
                if (event_session_id > -1)
                {
                    isdirty = true;
                    requiresdrop = true;
                }
            }
        }

        /// <summary>
        /// Specifies whether or not causality is tracked. If enabled, causality allows related events on different server connections to be correlated together.
        /// </summary>
        [CategoryAttribute("Session Options"), DescriptionAttribute("Specifies whether or not causality is tracked. If enabled, causality allows related events on different server connections to be correlated together.")]
        [XmlAttribute("TrackCausality")]
        public TrackCausality TrackCausality
        {
            get { return track_causality; }
            set
            {
                track_causality = value;
                if (event_session_id > -1)
                {
                    isdirty = true;
                    requiresdrop = true;
                }
            }
        }

        /// <summary>
        /// Specifies whether or not to start this event session automatically when SQL Server starts.
        /// </summary>
        [CategoryAttribute("Session Options"), DescriptionAttribute("Specifies whether or not to start this event session automatically when SQL Server starts.")]
        [XmlAttribute("StartupState")]
        public StartupState StartupState
        {
            get { return startup_state; }
            set
            {
                startup_state = value;
                if (event_session_id > -1)
                {
                    isdirty = true;
                    requiresdrop = true;
                }
            }
        }

        [XmlAttribute("SessionState")]
        public SessionState SessionState
        {
            get { return this.current_state; }
            set
            {
                current_state = value;
                if (event_session_id > -1)
                {
                    isdirty = true;
                    requiresdrop = true;
                }
            }
        }

        /// <summary>
        /// Event Session Requires Drop to make Changes
        /// </summary>
        [XmlIgnore()]
        public bool RequiresDrop
        {
            get { return requiresdrop; }
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Adds a EventSessionEvent to the EventSession.
        /// </summary>
        /// <param name="evnt">EventSessionEvent to Add</param>
        public bool AddEvent(EventSessionEvent evnt)
        {
            if (!this.events.Contains(evnt))
            {
                this.events.Add(evnt);
                if (this.event_session_id > -1)
                    isdirty = true;
                return true;
            }
            return false;
        }

        /// <summary>
        /// Removes the specified EventSessionEvent from the EventsCollection
        /// </summary>
        /// <param name="evnt">EventSessionEvent to Add to the EventsCollection</param>
        public bool RemoveEvent(EventSessionEvent evnt)
        {
            if (this.events.Contains(evnt))
            {
                this.events.Remove(evnt);
                if (this.event_session_id > -1)
                    isdirty = true;
                return true;
            }
            return false;
        }

        /// <summary>
        /// Adds a EventSessionTarget to the EventSession.
        /// </summary>
        /// <param name="target">EventSessionTarget to Add</param>
        public bool AddTarget(EventSessionTarget target)
        {
            if (!this.targets.Contains(target))
            {
                this.targets.Add(target);
                if (this.event_session_id > -1)
                    isdirty = true;
                return true;
            }
            return false;
        }

        /// <summary>
        /// Removes a EventSessionTarget from the EventSession.
        /// </summary>
        /// <param name="target">EventSessionTarget to Remove</param>
        public bool RemoveTarget(EventSessionTarget target)
        {
            if (this.targets.Contains(target))
            {
                this.targets.Remove(target);
                if (this.event_session_id > -1)
                    isdirty = true;
                return true;
            }
            return false;
        }

        #endregion

        #region Scripting Methods

        /// <summary>
        /// Generate a script for the Event Session in the Extended Events MetaData catalogs.
        /// </summary>
        public string GenerateSQLScript()
        {
            string script = string.Empty;

            if (CanGenerateScript())
            {
                if (this.event_session_id == -1)
                {
                    script = GenerateCreateScript();
                }
                else
                {
                    script = GenerateAlterScript();
                }
            }

            return script;
        }

        /// <summary>
        /// Generate a script to Drop the Event Session from the Extended Events MetaData catalogs.
        /// </summary>
        public string GenerateDropScript()
        {
            return string.Format(SqlCommandStrings.ScriptDropBase, name);
        }

        /// <summary>
        /// Generate a script to create the Event Session in the Extended Events MetaData catalogs.
        /// </summary>
        /// <returns>A TSQL Script to create the event session.</returns>
        /// <exception cref="System.ArgumentException">Throws an exception when a required part of an Event Session is missing.</exception>
        public string GenerateCreateScript()
        {
            string script = string.Empty;
            if (this.CanGenerateScript())
            {
                script = string.Format(SqlCommandStrings.ScriptDropBase + SqlCommandStrings.ScriptCreateBase, name);

                foreach (EventSessionEvent evnt in this.events)
                {
                    script += "\r\n" + evnt.Script();
                }

                script = Utilities.TrimScriptEnd(script);

                foreach (EventSessionTarget tgt in this.targets)
                {
                    script += "\r\n" + tgt.Script();
                }

                script = Utilities.TrimScriptEnd(script);

                script += "\r\n" + this.ScriptOptions();
                script = Utilities.TrimScriptEnd(script);
            }
            return script;
        }

        /// <summary>
        /// Generate a script to alter the session definition on the Server.
        /// </summary>
        /// <returns>A TSQL Script to alter the event session.</returns>
        /// <exception cref="System.ArgumentException">Throws an exception when a required part of an Event Session is missing.</exception>
        public string GenerateAlterScript()
        {
            string alterScript = string.Empty;

            if (this.CanGenerateScript())
            {
                EventSession original = (from se in Server.EventSessions
                                         where se.event_session_id == this.event_session_id
                                         select se).ElementAtOrDefault(0);
                if (original == null)
                {
                    alterScript = GenerateCreateScript();
                    return alterScript;
                }


                string dropScript = string.Empty;
                string addScript = string.Empty;

                if (this.isdirty)
                {
                    if (!requiresdrop) 
                    {
                        foreach (EventSessionEvent evt in original.Events)
                        {
                            if (!this.Events.Contains(evt))
                            {
                                dropScript += evt.ScriptDrop() + "\r\n";
                            }
                            bool that = this.Events.Contains(evt);

                        }
                        if (dropScript != string.Empty)
                        {
                            alterScript += Library.Utilities.TrimScriptEnd(string.Format(SqlCommandStrings.ScriptAlterEventSessionBase, original.Name) + dropScript);
                        }

                        dropScript = string.Empty;
                        foreach (EventSessionTarget tgt in original.Targets)
                        {
                            if (!this.Targets.Contains(tgt))
                            {
                                dropScript += tgt.ScriptDrop() + "\r\n";
                            }
                        }
                        if (dropScript != string.Empty)
                        {
                            alterScript += "\r\n\r\n" + Library.Utilities.TrimScriptEnd(string.Format(SqlCommandStrings.ScriptAlterEventSessionBase, original.Name) + dropScript);
                        }

                        foreach (EventSessionEvent evt in this.Events)
                        {
                            if (!original.Events.Contains(evt))
                            {
                                addScript += evt.Script() + "\r\n";
                            }
                        }
                        if (addScript != string.Empty)
                        {
                            alterScript += "\r\n\r\n" + Library.Utilities.TrimScriptEnd(string.Format(SqlCommandStrings.ScriptAlterEventSessionBase, original.Name) + addScript);
                        }

                        addScript = string.Empty;
                        foreach (EventSessionTarget tgt in this.Targets)
                        {
                            if (!original.Targets.Contains(tgt))
                            {
                                addScript += tgt.Script() + "\r\n";
                            }
                        }
                        if (addScript != string.Empty)
                        {
                            alterScript += "\r\n\r\n" + Library.Utilities.TrimScriptEnd(string.Format(SqlCommandStrings.ScriptAlterEventSessionBase, original.Name) + addScript);

                        }
                    }
                    else // If the name changed then drop the event and create a new one.
                    {
                        alterScript = original.GenerateDropScript() + this.GenerateCreateScript();
                    }
                }
                else
                    alterScript = GenerateCreateScript();
            }
            return alterScript;

        }

        public string AlterSessionStateScript(SessionState state)
        {
            return string.Format(Library.SqlCommandStrings.SessionStateSQL, this.Name, state.ToString().ToUpper());
        }

        /// <summary>
        /// Determines if the EventSession has all of its components for script generation.
        /// </summary>
        /// <exception cref="System.ArgumentException">Throws an exception when a required part of an Event Session is missing.</exception>
        private bool CanGenerateScript()
        {
            if ((this.events.Count > 0) && (this.targets.Count > 0) && (name != string.Empty))
                return true;

            ArgumentException ex = new ArgumentException();
            if (this.name == null || this.name == string.Empty)
                ex = new ArgumentException("The name specified is invalid for this session.");
            if (this.events.Count == 0)
                ex = new ArgumentException("At least one Event must be added to the session.");
            if (this.targets.Count == 0)
                ex = new ArgumentException("At least one Target must be added to the session.");

            throw ex;
        }

        #endregion

        #region Database Methods

        /// <summary>
        /// Start or Stop the Event Session on the SQL Server.
        /// </summary>
        /// <param name="state">SessionState enum for the new state to set.</param>
        public void ChangeSessionState(SessionState state)
        {
            Server.ServerConnection.ExecuteNonQuery(AlterSessionStateScript(state));
        }

        /// <summary>
        /// Drop the Event Session from the SQL Server Extended Events Engine.
        /// </summary>
        public void DropEventSession()
        {
            Server.ServerConnection.ExecuteNonQuery(GenerateDropScript());
        }

        /// <summary>
        /// Update the Extended Events Engine definition for this EventSession on the SQL Server.
        /// </summary>
        public void Update()
        {
            Server.ServerConnection.ExecuteNonQuery(GenerateSQLScript());
        }

        #endregion



        #region Private Methods


        internal static readonly string ScriptSessionOptionsBase = @"WITH (MAX_MEMORY = {0}KB, EVENT_RETENTION_MODE = {1}, MAX_DISPATCH_LATENCY = {2} SECONDS, MAX_EVENT_SIZE = {3}KB, MEMORY_PARTITION_MODE = {4}, TRACK_CAUSALITY = {5}, STARTUP_STATE = {6})";

        /// <summary>
        /// Generate the WITH script component for the SessionOptionCollection
        /// </summary>
        public string ScriptOptions()
        {
            return string.Format(ScriptSessionOptionsBase, max_memory, 
                event_retention_mode.ToString(), 
                max_dispatch_latency, 
                max_event_size, 
                memory_partition_mode.ToString(), 
                track_causality.ToString(), 
                startup_state.ToString());
        }

        #endregion
    }
}
